import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * Clase AnalizadorLexico
 * @author José Manuel Sánchez Bernabéu
 *         Mercedes Martinez Cortinas
 */
public class AnalizadorLexico {

    /**
     * Fichero de entrada del analizador
     */
    private RandomAccessFile fichero = null;
    /**
     * Longitud del fichero de entrada
     */
    public int longFichero;
    /**
     * Posicion en la cual se va a leer en la proxima pasada al fichero
     */
    private long posicionInicioToken;
    /**
     * Fila actual en la cual se encuentra el puntero
     */
    private int filaActual;
    /**
     * Columna siguiente en la cual se va a leer
     */
    private int columnaSiguiente;
    /**
     * Variable que indica si en la siguiente lectura vamos a leer el final del fichero para no entrar en el bucle del algoritmo
     */
    private boolean finalFichero;

    /**
     * @return devuelve el fichero
     */
    public RandomAccessFile getFichero() {
        return fichero;
    }

    /**
     * @param fichero actualiza el valor del fichero
     */
    public void setFichero(RandomAccessFile fichero) {
        this.fichero = fichero;
    }

    /**
     * Metodo que actualiza la posicion de inicio donde se empieza a leer del fichero
     * @param pos posicion donde empezaremos a leer del fichero
     */
    public void setposicionInicioToken(long pos) {
        this.posicionInicioToken = pos;
    }

    /**
     * Metodo que devuelve la posicion de inicio donde se empieza a leer del fichero
     * @return posicion de inicio de lectura
     */
    public long getposicionInicioToken() {
        return posicionInicioToken;
    }

    /**
     * Metodo que cambia el estado de la variable 'finalFichero' si hemos
     * llegado al final del fichero para que en la siguiente
     * lectura sepamos que tenemos que devolver un token de tipo EOF
     * @param estado 'True' si hemos llegado al final del fichero, 'False' en otro caso
     */
    public void setFinalFichero(boolean estado) {
        this.finalFichero = estado;
    }

    /**
     * Metodo que devuelve el valor de la variable finalFichero
     * que indica si hemos llegado al final del fichero
     * para que en la siguiente lectura sepamos que tenemos que
     * devolver un token de tipo EOF
     * @return estado 'True' si hemos llegado al final del fichero, 'False' en otro caso
     */
    public boolean getFinalFichero() {
        return finalFichero;
    }

    /**
     * @return devuelve al fila actual
     */
    public int getFilaActual() {
        return filaActual;
    }

    /**
     * @param filaActual modifica el valor de la fila actual
     */
    public void setFilaActual(int filaActual) {
        this.filaActual = filaActual;
    }

    /**
     * @return devuelve el valor de la columna siguiente que vamos
     * a leer
     */
    public int getcolumnaSiguiente() {
        return columnaSiguiente;
    }

    /**
     * @param columnaSiguiente modifica el valor de la
     * variable columna siguiente
     */
    public void setcolumnaSiguiente(int columnaSiguiente) {
        this.columnaSiguiente = columnaSiguiente;
    }

    /**
     * Constructor por defecto
     */
    public AnalizadorLexico()
    {}

    /**
     * Constructor sobrecargado del analizador lexico en el cual se le
     * pasa el fichero que vamos a leer
     * @param entrada Fichero que vamos a leer
     */
    public AnalizadorLexico(RandomAccessFile entrada)
    {
            try{
                setFichero(entrada);
                setposicionInicioToken(0);
                setFilaActual(1);
                setcolumnaSiguiente(1);
                longFichero = (int)getFichero().length(); // Establecemos la longitud del fichero
                finalFichero=false;
            }
            catch(IOException ex)
            {
                System.err.print("Error "+ex.getMessage());
            }
    }

    /**
     *  Metodo que devuelve el siguiente token que se lee
     * @return Token que devuelve
     */
    public Token siguienteToken()
    {
        Token token = new Token();
        int s=-1;
        int q=1,q1=-2; // inicializamos a estos estados por ej.
        String L="",P="";
        int sigma;

        try{
            getFichero().seek(getposicionInicioToken()); // Ponemos el puntero del fichero en la posicion donde nos habiamos quedado
            // Comprobamos si antes hemos llegado a final de fichero -> devolvemos un token de tipo EOF
            if(getFinalFichero() || esFinFichero())
            {
                    Token aux = new Token();
                    aux.setTipo(Token.EOF);
                    return aux;
            }
            // sino estamos ante el final del fichero
            do{
                    sigma=getFichero().read(); // leemos el siguiente caracter
                    //char b = (char)sigma; // Lo usamos para ver la traduccion en el debug
                    if(sigma==-1)
                    {
                    	if(s!=-1) // si hemos llegado a final de fichero y nos queda algo por devolver...
                    	{
                            token.setLexema(L); // Asignamos lexema
                            token.setFila(getFilaActual()); // Asignamos fila
                            token.setColumna(getcolumnaSiguiente() - L.length()); // Asignamos Columna
                            token.setTipo(ObtenerTipo(q, L));
                            setposicionInicioToken(getFichero().getFilePointer());
                            setFinalFichero(true);
                    	}
                        else if(q==1)
                        {
                            Token aux = new Token();
                            aux.setTipo(Token.EOF);
                            return aux;
                        }
                    }
                    setcolumnaSiguiente(getcolumnaSiguiente()+1);
                    P+=(char)sigma;
                    q1=delta(q,(char)sigma);

                    if(q1!=10) // si no estamos en un comentario
                    {
                        if(q1==7 || q1==8 || q1==10 || q1==11 || q1==12) // Si leemos un \n o \t o ' ' o estamos en un comentario salimos del bucle y descartamos lo leido
                        {
                            s=-1;
                            if(q1!=9 && q1!=11) // si no estamos en un comentario
                                q=q1=1;
                            else    // si estamos en un comentario nuestro estado q sera el mismo que q1
                                q=q1;
                            L=P="";
                            token = new Token();
                            setposicionInicioToken(getFichero().getFilePointer());
                        }
                        else
                        {
                            if(q1==-1) // Si hemos llegado a un estado final
                            {
                                  if(s!=-1) // Si estabamos en un estado de aceptacion
                                  {
                                        /*if(esFinFichero()) // Si leemos final de fichero devolvemos el ultimo token que hemos encontrado y despues empezamos desde el final del fichero
                                        {
                                            setposicionInicioToken(getFichero().getFilePointer());
                                            setFinalFichero(true);
                                        }
                                        else // Si no hemos llegado a final de fichero colocamos el puntero en una posicion anterior*/
                                        //if(!getFinalFichero())
                                        setposicionInicioToken(getFichero().getFilePointer()-1);

                                        setcolumnaSiguiente(getcolumnaSiguiente()-1);
                                        token.setLexema(L); // Asignamos lexema
                                        token.setFila(getFilaActual()); // Asignamos fila
                                        token.setColumna(getcolumnaSiguiente() - L.length()); // Asignamos Columna
                                        token.setTipo(ObtenerTipo(q, L));
                                  }
                                  else // Error en caso de leer un caracter no valido
                                  {
                                        //System.err.println("Error 1 ("+getFilaActual()+","+(getcolumnaSiguiente()-P.length())+"): caracter '"+(char)sigma+"' incorrecto");
                                        System.err.println("Error 1 ("+getFilaActual()+","+(getcolumnaSiguiente()-P.length())+"): caracter '"+P.charAt(0)+"' incorrecto");
                                        System.exit(-1);
                                  }
                            } // Si hemos llegado a un estado final nos quedamos con la parte buena de la cadena L
                            else if(esEstadoFinal(q1))
                            {
                                  s=q1;
                                  L+=P;
                                  P="";
                            }
                            q=q1; // nos quedamos en el estado en el que estabamos
                        }
                    }
                    else // si estamos en un comentario nos quedamos en el estado del automata en el que estabamos
                    {
                        if(esFinFichero()) // Si leemos final de fichero devolvemos el ultimo token que hemos encontrado y despues empezamos desde el final del fichero
                        {
                             System.err.println("Error 2: fin de fichero inesperado");
                             System.exit(-1);
                        }
                        q=q1;
                    }
            }while(q1!=-1);
            
            return token;
        }
        catch(IOException ex)
        {
            System.err.print("Error: "+ ex.getMessage());
        }
        return token;
    }

    /**
     * Metodo que se desplaza desde un estado a otro con un caracter recibido
     * @param estado estado actual
     * @param c caracter que se le pasa al estado actual
     * @return estado al que pasa con el caracter leido
     */
    public int delta(int estado, char c)
    {
        switch(estado) {
            case 1: if(c>=48 && c<=57) return 2; // Si empezamos a leer REAL
                    else if((c>='a' && c<='z') || (c>='A' && c<='Z')) return 5; // Si empezamos a leer ID
                    else if(c=='{' || c=='}' || c=='=' || c==';' || c=='(' || c==')') return 6; // Si leemos caracteres {  }  =  ;  (  )
                    else if(c=='\n' || c=='\r') { // Si leemos un \n o un \r
                            setcolumnaSiguiente(1);
                            setFilaActual(getFilaActual()+1);
                            return 7;
                    }
                    else if(c=='\t' || c==' ') return 8; // Si leemos un \t o espacio
                    else if(c=='/') return 9;
                    else return -1;
            case 2: if((c>=48 && c<=57)) return 2; // si leemos un digito c > 47 (ASCCI del 0)  y c < 57 (ASCII de 9)
                    else if(c=='.') return 3;
                    else return -1;
            case 3: if(c>=48 && c<=57) return 4;
                    else return -1;
            case 4: if(c>=48 && c<=57) return 4;
                    else return -1;
            case 5: if((c>='a' && c<='z') || (c>='A' && c<='Z') || (c>=48 && c<=57)) return 5;
                    else return -1;
            case 6:
            case 7:
            case 8: return -1;
            // Casos para los comentarios
            case 9: if(c=='*') return 10; else return -1;
            case 10:if(c=='*') return 11;
                    else if(c=='\n' || c=='\r') { // Si leemos un \n o un \r
                            setcolumnaSiguiente(1);
                            setFilaActual(getFilaActual()+1);
                            return 10;
                          }
                          else return 10;
	    case 11:if(c=='\n' || c=='\r') { // Si leemos un \n o un \r
                            setcolumnaSiguiente(1);
                            setFilaActual(getFilaActual()+1);
                            return 10;
                          }
                    else if(c=='/') return 12; else if(c=='*') return 11; else return 10;
            case 12:return -1; // si se termina el comentario
            default: /*error fatal*/
        }

        return -1;
    }

    /**
     * Metodo que comprueba si se ha llegado a un estado de aceptacion el automata
     * @param q1 estadoActual
     * @return True si es estado de aceptacion, False en otro caso
     */
    public boolean esEstadoFinal(int q1)
    {
        if(q1==4 || q1==5 || q1==6 || q1==7 || q1==8 || q1==-1 || q1==12)
            return true;
        return false;
    }

    /**
     * Metodo que comprueba si hemos llegado al final de fichero
     * @return True si ha llegado al final del fichero, False en otro caso
     * @throws IOException
     */
    public boolean esFinFichero() throws IOException
    {
	boolean retorno = false;
	if(getFichero().getFilePointer()==getFichero().length())
		retorno = true;

	return retorno;
    }

    /**
     * Metodo que devuelve el tipo de token
     * @param estadoQ estado de aceptacion
     * @param lexema lexema para indicar el tipo que tiene
     * @return Token.Tipo Tipo enum que indica el tipo del token
     */
    public int ObtenerTipo(int estadoQ, String lexema)
    {
        switch(estadoQ)
        {
            case 4: return Token.REAL;
            case 5: if(!lexema.equals("class") && !lexema.equals("double") && !lexema.equals("void")) return Token.ID;
                    else if(lexema.equals("class")) return Token.CLASS;
                    else if(lexema.equals("double")) return Token.DOUBLE;
                    else if(lexema.equals("void")) return Token.VOID;
            case 6: if(lexema.equals("(")) return Token.PARI;
                    else if(lexema.equals(")")) return Token.PARD;
                    else if(lexema.equals("{")) return Token.LLAVEI;
                    else if(lexema.equals("}")) return Token.LLAVED;
                    else if(lexema.equals("=")) return Token.ASIG;
                    else if(lexema.equals(";")) return Token.PYC;
        }
        return -1;
    }
}
